#include "./conn.h"
#include "../driver/cmd_uart.h"
#include "../utils/alg.h"
#include "../utils/byte_alloc.h"
#include "./simple_req.h"
#include "../task/handler_task.h"

namespace service {

using namespace driver;
using namespace conn;
using namespace utils;
using namespace task;

namespace proto_parse {

static ByteView bw;
static constexpr uint32_t RECV_TIMEOUT = 20;
static ByteAlloc<320> bytes;

static bool recv_if_empty(uint32_t timeout = osWaitForever) {
    return (bw.len != 0) || (osOK == cmdUart.poll(bw, timeout));
}

bool take_byte(uint8_t &value) {
    if (!recv_if_empty(RECV_TIMEOUT)) {
        return false;
    }
    value = bw.buf[0];
    bw.buf += 1;
    bw.len -= 1;
    return true;
}

bool sync() {
    bool flag = false;
    uint8_t value;
    for (;;) {
        if (!take_byte(value)) {
            return false;
        }
        if (flag && (value == proto::HEAD1)) {
            return true;
        }
        flag = value == proto::HEAD0;
    }
}

bool take(uint8_t *dst, uint8_t len) {
    for (uint8_t index = 0; index < len;) {
        if (!recv_if_empty(RECV_TIMEOUT)) {
            return false;
        }
        uint8_t n = len - index;
        if (n > bw.len) {
            n = bw.len;
        }
        memcpy(dst + index, bw.buf, n);
        bw.buf += n;
        bw.len -= n;
        index += n;
    }
    return true;
}

Message recv() {
    uint8_t len;
    uint8_t *buf;
    for (;;) {
        recv_if_empty();
        if (!sync()) {
            continue;
        }
        if (!take_byte(len)) {
            continue;
        }
        if (len < 7) {
            continue;
        }
        len -= 3;
        buf = bytes.alloc(len);
        if (!take(buf, len)) {
            continue;
        }

        uint8_t sum = alg::xor_sum(buf, len - 2);
        if (buf[len - 2] != sum) {
            continue;
        }
        if (buf[len - 1] != proto::END) {
            continue;
        }

        Message msg(buf, len - 2);
        return msg;
    }
}

} // namespace proto_parse

using namespace proto_parse;

namespace dispatch {

static bool session_flag = false;

void session(const Message &msg) {
    session_flag = true;
    cmdUart.ack(msg.seq());
}

void req(const Message &msg) {
    cmdUart.ack(msg.seq());
    if (!session_flag) {
        return;
    }
    session_flag = false;
    bytes.acquire(msg.len());
    handlerTask.post(msg);
}

} // namespace dispatch

void conn_loop() {

    for (;;) {
        Message msg = recv();
        switch (msg.type()) {
        case proto::TYPE_ACK:
            cmdUart.on_ack();
            break;
        case proto::TYPE_PING:
            cmdUart.pong(msg.seq());
            break;
        case proto::TYPE_SESSION:
            dispatch::session(msg);
            break;
        case proto::TYPE_SIMPLE_REQ:
            simple_req::dispatch(msg);
            break;
        case proto::TYPE_REQ:
            dispatch::req(msg);
            break;
        }
    }
}

} // namespace service
